﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;
using System.Threading;
using System.Security.Cryptography;

using Microsoft.Win32;
using System.IO.Pipes;

namespace RentIt06WPFClient
{
    /// <summary>
    /// Interaction logic for UploadWindow.xaml
    /// </summary>
    public partial class UploadWindow : Window
    {
        private PublisherSession.Media media;
        private readonly Button creator;
        private String filePath;

        private byte[] calculatedHash;

        private EventWaitHandle waitHandle;

        private int uploadToken;

        public UploadWindow(PublisherSession.Media media, Button creator)
        {
            this.media = media;
            this.creator = creator;
            creator.IsEnabled = false;
            InitializeComponent();
            Show();
        }

        private void browseFiles(object sender, RoutedEventArgs e)
        {
            // use the built in file chooser to find a media for upload
            var fileDialog = new OpenFileDialog();
            if (fileDialog.ShowDialog(this) == true)
            {
                filePathLabel.Content = fileDialog.FileName;
                filePath = fileDialog.FileName;
                uploadButton.IsEnabled = true;
            }
            else
                uploadButton.IsEnabled = false;
        }

        private void startUpload(object sender, RoutedEventArgs e)
        {
            uploadButton.IsEnabled = false;
            // que upload process to avoid freezing the interface while uploading
            ThreadPool.QueueUserWorkItem(this.uploadProgressThread);
        }

        private void uploadProgressThread(object obj)
        {
            waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
            calculatedHash = null;

            using (var file = new FileStream(filePath, FileMode.Open))
            {
                // create pipes to verify the hash. If we copied directly from the file stream we would not be able
                // to track the progress of the hash.
                var toHash = new AnonymousPipeServerStream();
                var hashThreadStream = new AnonymousPipeClientStream(PipeDirection.In, toHash.ClientSafePipeHandle);

                // que the verification task
                ThreadPool.QueueUserWorkItem(calculateHashAsync, hashThreadStream);

                hashProgress.Dispatcher.Invoke(new Action (
                    delegate {
                        hashProgress.Maximum = file.Length;
                    }));

                // start the transfer/tracker process to read from the file to the verification thread while updating the hash progress bar
                bufferStream(file, toHash, hashProgress);
            }

            // If calculating the hash takes more than 60 seconds AFTER the file is loaded from the hard drive some error has occured in the hash thread
            waitHandle.WaitOne(60000);
            if (calculatedHash == null)
            {
                uploadButton.Dispatcher.Invoke(new Action (
                    delegate {
                        uploadButton.IsEnabled = true;
                    }));
                MessageBox.Show("Could not verify the file for upload.");
                return;
            }

            // when the hash i calculated we can request an upload token from the publisher session
            uploadToken = Session.Publisher.GetUploadToken(media.Id, calculatedHash);

            waitHandle.Reset();

            using (var file = new FileStream(filePath, FileMode.Open))
            {
                // create a pipe to upload the file. This is the same as happens in the hashing process
                var toServer = new AnonymousPipeServerStream();
                var serverThreadStream = new AnonymousPipeClientStream(PipeDirection.In, toServer.ClientSafePipeHandle);

                ThreadPool.QueueUserWorkItem(this.uploadFileAsync, serverThreadStream);

                uploadProgress.Dispatcher.Invoke(new Action(
                    delegate {
                        uploadProgress.Maximum = file.Length;
                    }));

                // start the transfer/tracker process to read from the file to the upload thread while updating the upload progress bar
                bufferStream(file, toServer, uploadProgress);
            }

            // wait for the server to read the final buffer and close the file stream after upload
            finalizing.Dispatcher.Invoke(new Action(
                    delegate {
                        finalizing.IsIndeterminate = true;
                    }));
            waitHandle.WaitOne();

            Dispatcher.Invoke(new Action(
                delegate {
                    finalizing.IsIndeterminate = false;
                    finalizing.Value = finalizing.Maximum;
                    MessageBox.Show("File uploaded successfully!", "Success", MessageBoxButton.OK);
                    Close();
                }));
        }

        private void bufferStream(Stream from, AnonymousPipeServerStream to, ProgressBar status)
        {
            const int bufferLen = 65000;
            var buffer = new byte[bufferLen];
            int count = 0;
            // read from one stream in chunks and write the chunk to another stream while updating the progress bar
            while ((count = from.Read(buffer, 0, bufferLen)) > 0)
            {
                status.Dispatcher.Invoke(new Action(
                    delegate {
                        status.Value += count;
                    }));
                to.Write(buffer, 0, count);
                to.WaitForPipeDrain();
            }

            from.Close();
            to.Close();
        }

        private void closedEvent(object sender, EventArgs e)
        {
            creator.IsEnabled = true;
        }

        private void calculateHashAsync(Object obj)
        {
            var stream = (Stream)obj;

            // calculate the hash
            SHA512 sha = SHA512.Create();
            byte[] computed = sha.ComputeHash(stream);

            calculatedHash = computed;
            // release the wait handle when the hash i fully calculated
            waitHandle.Set();
        }

        private void uploadFileAsync(Object obj)
        {
            var stream = (Stream)obj;
            // upload the file
            Session.FileService.UploadMedia(uploadToken, stream);
            // release the wait handle when the file is fully uploaded
            waitHandle.Set();
        }
    }
}
